<?php
class LockException extends Exception {}

class LockUtil
{/*{{{*/
    const LOCK_TYPE_DB = 'SQLLock';
    const LOCK_TYPE_FILE = 'FileLock';
    const LOCK_TYPE_MEMCACHE = 'MemcacheLock';

    private static $_availableLocks = array('FileLock', 'SQLLock', 'MemcacheLock');

    private function __construct(){}

    static public function factory($type, $options=array())
    {
        if (false == in_array($type, self::$_availableLocks))
        {
            throw new LockException('no this ['.$type.'] locker!');
        }
        static $ins = array();
        $key = md5($type.serialize($options));
        $locker = isset($ins[$key])?$ins[$key]:null;
        if (false == $locker instanceof self)
        {
            $locker = new $type($options);
            $ins[$key] = $locker;
        }
        return $locker;
    }
}/*}}}*/

interface Lockable
{/*{{{*/
    const EXPIRE = 3;
    public function getLock($key, $timeout=self::EXPIRE);
    public function releaseLock($key);
}/*}}}*/

class FileLock implements Lockable
{/*{{{*/
    private $_fp;
    private $_map;
    private $_single;

    public function __construct($options)
    {/*{{{*/
        if (isset($options['path']) && is_dir($options['path']))
        {
            $this->_lockPath = $options['path'].'/';
        }
        else
        {
            $this->_lockPath = '/tmp/';
        }
        $this->_single = isset($options['single'])?$options['single']:false;
    }/*}}}*/

    public function getLock($key, $timeout=self::EXPIRE)
    {/*{{{*/
        $file = md5(__FILE__.$key);
        $this->fp = fopen($this->_lockPath.$file.'.lock', "w+");
        if (true || $this->_single)
        {
            $op = LOCK_EX + LOCK_NB;
        }
        else
        {
            $op = LOCK_EX;
        }
        if (false == flock($this->fp, $op, $a))
        {
            throw new LockException('failed');
        }
    return true;
    }/*}}}*/

    public function releaseLock($key)
    {/*{{{*/
        flock($this->fp, LOCK_UN);
        fclose($this->fp);
    }/*}}}*/
}/*}}}*/

class SQLLock implements Lockable
{/*{{{*/
    public function __construct($options)
    {/*{{{*/
        $this->_driver = matrix::get('dbexecuter'); 
    }/*}}}*/

    public function getLock($key, $timeout=self::EXPIRE)
    {/*{{{*/
        $sql = "SELECT GET_LOCK('".$key."', '".$timeout."')";
        return $this->_driver->query($sql);
    }/*}}}*/

    public function releaseLock($key)
    {/*{{{*/
        $sql = "SELECT RELEASE_LOCK('".$key."')";
        return $this->_driver->query($sql);
    }/*}}}*/
}/*}}}*/

